#include "stdafx.h"
#include "ListDataTypes.h"
#include "PhoneUtils.h"
#include "Settings.h"
#include "Language.h"
#include "PoomData.h"
#include "ListDataCallLog.h"
#include "GraphicFunctions.h"
#include "Theme.h"
#include "iContact.h"


#ifdef _DEBUG
#include "crtdbg.h"
#endif

CListItemBase::CListItemBase() : oId(0), picture(NULL), nPrimaryTextLength(0), nDialingNumberLength(0), nDialingNumberLengthDisplay(0), wcGroup(0)
{
  szPrimaryText[0] = 0; szDialingNumber[0] = 0; szDialingNumberDisplay[0] = 0;
}

void CListItemBase::Clear()
{
  oId = 0;
  nPrimaryTextLength = 0; nDialingNumberLength = 0; nDialingNumberLengthDisplay = 0;
  picture = NULL; wcGroup = 0;
  szPrimaryText[0] = 0; szDialingNumber[0] = 0; szDialingNumberDisplay[0] = 0;

  CListItem::Clear();
}

void CListItemBase::OptDrawItemOn(HWND hParent, HDC hdc, const RECT& rItem, OptDrawItem odi) const
{
  switch (odi)
  {
  case odiBackground:
    OptDrawBackground(hParent, hdc, rItem);
    break;
  case odiGroupHeader:
    OptDrawGroup(hParent, hdc, rItem);
    break;
  case odiPrimaryText:
    OptDrawPrimary(hParent, hdc, rItem);
    break;
  case odiDialingNo:
    OptDrawDialingNo(hParent, hdc, rItem);
    break;
  case odiPicture:
    OptDrawPicture(hParent, hdc, rItem);
    break;
  }
}

void CListItemBase::OptDrawBackground(HWND hParent, HDC hdc, const RECT& rItem) const
{
  // Item Background
  if (this->GetHovered())
    GraphicFunctions::DrawGradientGDI(hdc, rItem, Theme::rgbListItemSelectedBackground1, Theme::rgbListItemSelectedBackground2);
  else
    FillRect(hdc, &rItem, Theme::hbrListItemBackground);

  // separator
  RECT rect = rItem;
  rect.top = rItem.bottom - LIST_SEPARATOR_HEIGHT;
  FillRect(hdc, &rect, Theme::hbrListItemSeparator);
}

void CListItemBase::OptDrawGroup(HWND hParent, HDC hdc, const RECT& rItem) const
{
  TCHAR buffer[GROUP_TEXT_LENGTH];
  GetGroup(hParent, buffer);
  int length = _tcslen(buffer);

  if (length == 0)
    return;

  RECT rHeader = rItem;
  rHeader.bottom = rHeader.top + Theme::iListGroupSize;

  // ****** GroupHeader background
  FillRect(hdc, &rHeader, Theme::hbrListGroupBackground);

  // separator
  RECT rSep = rHeader;
  rSep.top = rHeader.bottom - LIST_SEPARATOR_HEIGHT;
  FillRect(hdc, &rSep, Theme::hbrListItemSeparator);

  // ******* Draw Group Header Text
  SetTextColor(hdc, Theme::rgbListGroupText);
  ExtTextOut(hdc, rItem.left + Theme::iListGroupItemIndent, 
        rHeader.top - 1 + ((Theme::iListGroupSize - Theme::GroupFontSize) / 2),
        NULL, NULL, buffer, length, 0);
}

void CListItemBase::OptDrawPrimary(HWND hParent, HDC hdc, const RECT& rItem) const
{
  bool hasPicture = (picture != NULL && picture->hBitmap != NULL);

  RECT rect = rItem;
  // Item Primary Text
  rect.left += Theme::iListItemIndent;
  if (hasPicture)
    rect.right -= Theme::ListPictureWidth + Theme::iListItemIndent * 2;
  if ( Settings::showDialingNumber && nDialingNumberLength > 0 && !Settings::showDialNoAfterPrimaryText )
    rect.top = rItem.bottom - 2 - ((Theme::iListItemSize + Theme::DialingNoListFontSize + Theme::PrimaryListFontSize) / 2);
  else
    rect.top = rItem.bottom - 2 - ((Theme::iListItemSize + Theme::PrimaryListFontSize) / 2);

  rect.bottom = rect.top + Theme::PrimaryListFontSize;

  // Primary Text Shadow
  if (this->GetHovered())
  {
    SetTextColor(hdc, Theme::rgbListItemSelectedShadow);
    rect.top += 2;
    rect.bottom += 2;
    if (Settings::doFastGraphics)
      ExtTextOut(hdc, rect.left, rect.top, 0, NULL, szPrimaryText, nPrimaryTextLength, 0);
    else
      ExtTextOut(hdc, rect.left, rect.top, ETO_CLIPPED, &rect, szPrimaryText, nPrimaryTextLength, 0);
    rect.top -= 2;
    rect.bottom -= 2;
  }
  //
  // Primary Text
  //
  // set color
  SetTextColor(hdc, GetPrimaryColor(hParent));
  // draw text
  if (Settings::doFastGraphics)
    ExtTextOut(hdc, rect.left, rect.top, 0, NULL, szPrimaryText, nPrimaryTextLength, 0);
  else
    ExtTextOut(hdc, rect.left, rect.top, ETO_CLIPPED, &rect, szPrimaryText, nPrimaryTextLength, 0);
}

void CListItemBase::OptDrawDialingNo(HWND hParent, HDC hdc, const RECT& rItem) const
{
  // Item Dialing Number
  if (!Settings::showDialingNumber || nDialingNumberLength == 0)
    return;

  RECT rect = rItem;
  bool hasPicture = (picture != NULL && picture->hBitmap != NULL);


  // @ADD 2010-06-12  Nocky: Display dialing number after primary text
  if( Settings::showDialNoAfterPrimaryText ){
	  // Item Primary Text
	  rect.left += Theme::iListItemIndent;
	  rect.left = rItem.right - ((Theme::DialingNoListFontSize * nDialingNumberLengthDisplay) / 2) - 2;
	  rect.top = rItem.bottom - 4 - ((Theme::iListItemSize + Theme::DialingNoListFontSize) / 2);
  }
  else
  {
	  rect.left += Theme::iListItemIndent;
	  if (hasPicture)
		  rect.right -= Theme::ListPictureWidth + Theme::iListItemIndent;
	  rect.top = rItem.bottom - 2 - ((Theme::iListItemSize + Theme::DialingNoListFontSize - Theme::PrimaryListFontSize) / 2);
	  rect.bottom = rect.top + Theme::DialingNoListFontSize;
  }


  SetTextColor(hdc, this->GetHovered() ? Theme::rgbListItemSelectedText : Theme::rgbListItemNumberText);

  if (Settings::doFastGraphics)
  {
    ExtTextOut(hdc, rect.left, rect.top, 0, NULL, Settings::showDialingNumberSuffix?szDialingNumberDisplay:szDialingNumber, Settings::showDialingNumberSuffix?nDialingNumberLengthDisplay:nDialingNumberLength, 0);
  }
  else
  {
    ExtTextOut(hdc, rect.left, rect.top, ETO_CLIPPED, &rect, Settings::showDialingNumberSuffix?szDialingNumberDisplay:szDialingNumber, Settings::showDialingNumberSuffix?nDialingNumberLengthDisplay:nDialingNumberLength, 0);
  }
}

void CListItemBase::OptDrawPicture(HWND hParent, HDC hdc, const RECT& rItem) const
{
  if ((picture == NULL || picture->hBitmap == NULL) && GetType() != citSim)
    return;

  RECT rect = rItem;
  // Item Primary Text
  rect.right -= Theme::ListPictureWidth + Theme::iListItemIndent;

  if (picture != NULL && picture->hBitmap != NULL)
  { // actual picture is present
    rect.left = rect.right + (Theme::ListPictureWidth - picture->width) / 2;
    rect.top = rItem.bottom - (Theme::ListPictureHeight + picture->height) / 2;
    Theme::hbmTemp = (HBITMAP)SelectObject(Theme::hdcTemp, picture->hBitmap);
    if (Settings::enablePicturesTransparency)
    {
      COLORREF transpCol = GetPixel(Theme::hdcTemp, 0, 0);
      TransparentBlt(hdc, rect.left, rect.top, picture->width, picture->height,
                     Theme::hdcTemp, 0, 0, picture->width, picture->height, transpCol);
    }
    else
    {
      BitBlt(hdc, rect.left, rect.top, picture->width, picture->height, Theme::hdcTemp, 0, 0, SRCCOPY);
    }
    SelectObject(Theme::hdcTemp, Theme::hbmTemp);
  }
  else if (GetType() == citSim)
  { 
    // SIM picture
    rect.left = rect.right + (Theme::ListPictureWidth - Theme::SpecialIconsWidth) / 2;
    rect.top = rItem.bottom - (Theme::ListPictureHeight + Theme::SpecialIconsHeight) / 2;
    Theme::DrawSpecialIcon(hdc, rect.left, rect.top, siSimContact);
  }
}

COLORREF CListItemBase::GetPrimaryColor(HWND hParent) const
{
  COLORREF primClr;

  if (this->GetHovered())
    primClr = Theme::rgbListItemSelectedText;
  else
    primClr = Theme::rgbListItemText;

  return primClr;
}

COLORREF CListItemBase::GetSecondaryColor(HWND hParent) const  // Avian - done so that highlighted items get the selection color
{
  COLORREF secClr;

  if (this->GetHovered())
    secClr = Theme::rgbListItemSelectedText;
  else
    secClr = Theme::rgbListItemSecondaryText;

  return secClr;
}

COLORREF CListItemBase::GetSecondary2Color(HWND hParent) const  // Avian - done so that highlighted items get the selection color
{
  COLORREF secClr;

  if (this->GetHovered())
    secClr = Theme::rgbListItemSelectedText;
  else
    secClr = Theme::rgbListItemSecondaryText2;

  return secClr;
}

bool CListItemBase::OnMouseClick(HWND hParent, int x, int y)
{
  PostCommand(hParent, CMD_LIST_SHOW_DETAILS);
  return true;
}

bool CListItemBase::OnKeyUp(HWND hParent, int vKey)
{
  switch(vKey)
  {
  case VK_TACTION:
    PostCommand(hParent, CMD_LIST_SHOW_DETAILS);
    return true;

  case VK_TTALK:
    if (Settings::enablePhoneKeyHook)
      return OnDefaultAction(hParent);
  }

  return false;
}

bool CListItemBase::OnLongTap(HWND hParent)
{
  return OnDefaultAction(hParent);
}

bool CListItemBase::OnDefaultAction(HWND hParent)
{
  if (nDialingNumberLength > 0)
  {
    PhoneUtils::Call(szDialingNumber, szPrimaryText);

    if (Settings::doMinimizeOnAction)
    {
      SetWindowPos(hParent, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
      ShowWindow(hParent, SW_HIDE);
    }
    if (Settings::doExitOnAction)
    {
      DestroyWindow(hParent);
    }
  }
  else
  {
    PostCommand(hParent, CMD_LIST_SHOW_DETAILS);
  }

  return true;
}

// szOutName must be pre-allocated with length PRIMARY_TEXT_LENGTH
bool CListItemBase::GetNameFromNumber(HWND hParent, TCHAR* szOutName, const TCHAR* szNumber, bool searchInList) const
{
  CEOID oid = CListItemBase::FindOid(hParent, NULL, szNumber, searchInList);
  bool ret = false;

  if (oid != 0)
  {
    IContact* pContact = NULL;

    if (SUCCEEDED(PoomData::GetApp()->GetItemFromOid(oId, reinterpret_cast<IDispatch**>(&pContact))))
    {
      BSTR bstr = NULL;
      if (SUCCEEDED(pContact->get_FileAs(&bstr)))
      {
        ::StringCchCopy(szOutName, PRIMARY_TEXT_LENGTH, bstr);
        ret = true;
      }
      SysFreeString(bstr);
      pContact->Release();
    }
  }

  return ret;
}

CEOID CListItemBase::FindOid(HWND hParent, const TCHAR* szName, const TCHAR* szNumber, bool searchInList) const
{
  // first look into our own list
  if (searchInList)
  {
    CListBase* pContactsList = Info_GetContactsList(hParent);
    if (pContactsList)
    {
	  int n = pContactsList->GetCount();
      for (int i = 0; i < n; i++)
      {
        const CListItemBase* pItem = (CListItemBase*)pContactsList->GetAt(i);
        if (pItem == NULL || pItem->oId == 0)
          continue;
        if (szNumber != NULL && pItem->nDialingNumberLength > 0)
        {
          if (_tcscmp(pItem->szDialingNumber, szNumber) == 0)
            return pItem->oId;
        }
        else if (szName != NULL && pItem->nPrimaryTextLength > 0)
        {
          if (_tcscmp(pItem->szPrimaryText, szName) == 0)
            return pItem->oId;
        }
      }
    }
  }

  // then search in database
  CEOID oId = 0;
  HRESULT hr;
  IItem* pContact = NULL;
  CEPROPID propId = 0;
  if (szNumber && *szNumber)
    hr = FindMatchingContact(PoomData::GetApp(), szNumber, FMCF_FINDPHONE, &pContact, &propId);
  else if (szName && *szName)
    hr = FindMatchingContact(PoomData::GetApp(), szName, FMCF_FINDFILEAS, &pContact, &propId);
  else
    hr = E_FAIL;
  if (SUCCEEDED(hr))
  {
    long lOid;
    hr = pContact->get_Oid(&lOid);
    if (SUCCEEDED(hr))
    {
      oId = lOid;
    }
    pContact->Release();
  }

  return oId;
}

CListItemCallLog::CListItemCallLog()
  : cEntry(0), nSecondaryTextLength(0), nSecondaryText2Length(0)
{
  szSecondaryText[0] = 0; szSecondaryText2[0] = 0;
}

void CListItemCallLog::Clear()
{
  cEntry = NULL;
  szSecondaryText[0] = 0; szSecondaryText2[0] = 0;
  nSecondaryTextLength = 0; nSecondaryText2Length = 0;

  CListItemBase::Clear();
}

bool CListItemCallLog::IsCommandValid(MenuCommands eCmd)
{
  switch(eCmd)
  {
  case CMD_DETAILS_BACK:
    return true;
  // Call Log Details
  case CMD_DETAILS_CL_NEWCONTACT:
    return oId == 0;
  case CMD_DETAILS_CL_GOTOCONTACT:
    return oId != 0;
  case CMD_DETAILS_CL_DELETECALL:
    return cEntry ? cEntry->oId != 0 : false;
  }

  return false;
}

bool CListItemCallLog::OnCommand(HWND hParent, MenuCommands eCmd)
{
  switch(eCmd)
  {
  case CMD_DETAILS_CL_NEWCONTACT:
    if (cEntry != NULL)
    {
      return PoomData::CreateContact(cEntry->szName, cEntry->szNumber);
    }
    break;

  case CMD_DETAILS_CL_GOTOCONTACT:
    break;

  case CMD_DETAILS_CL_DELETECALL:
    if (cEntry != NULL && cEntry->oId != 0)
    {
      return EdbData::DeleteRecord(EDB_CLOG, cEntry->oId);
      // notification will come and list will be reloaded automatically
    }
    break;
  }

  return false;
}

void CListItemCallLog::GetGroup(HWND hParent, TCHAR* pszGroup) const
{
  TCHAR g = wcGroup;
  StringCchCopy(pszGroup, GROUP_TEXT_LENGTH, 
      g == 1 ? Language::sunday
    : g == 2 ? Language::monday
    : g == 3 ? Language::tuesday
    : g == 4 ? Language::wednesday
    : g == 5 ? Language::thursday
    : g == 6 ? Language::friday
    : g == 7 ? Language::saturday
    : g == 'T' ? Language::today
    : g == 'Y' ? Language::yesterday
    : Language::older
  );
}

void CListItemCallLog::GetGroupIndicator(HWND hParent, TCHAR * pszGroup) const
{
  if (!Settings::showScrollIndInHistory)
  {
    pszGroup[0] = 0;
    return;
  }

  TCHAR g = wcGroup;
  StringCchCopy(pszGroup, GROUP_TEXT_LENGTH, 
      g == 1 ? Language::sunday
    : g == 2 ? Language::monday
    : g == 3 ? Language::tuesday
    : g == 4 ? Language::wednesday
    : g == 5 ? Language::thursday
    : g == 6 ? Language::friday
    : g == 7 ? Language::saturday
    : g == 'T' ? Language::today
    : g == 'Y' ? Language::yesterday
    : Language::older
  );
}

COLORREF CListItemCallLog::GetPrimaryColor(HWND hParent) const
{
  COLORREF primClr;

  if (this->GetHovered())
    primClr = Theme::rgbListItemSelectedText;
  else if (cEntry && Info_GetCurrentTab(hParent) == tabCallLog && Info_GetCallType(hParent) == ctAll)
  {
    switch(cEntry->callType)
    {
    case ctMissed:   primClr = Theme::rgbCallHistoryMissed;   break;
    case ctIncoming: primClr = Theme::rgbCallHistoryIncoming; break;
    case ctOutgoing: primClr = Theme::rgbCallHistoryOutgoing; break;
    default:         primClr = Theme::rgbListItemText;        break;
    }
  }
  else
    primClr = Theme::rgbListItemText;

  return primClr;
}

void CListItemCallLog::OptDrawItemOn(HWND hParent, HDC hdc, const RECT& rItem, OptDrawItem odi) const
{
  RECT rc = rItem;
  if (nSecondaryTextLength > 0)
    rc.right -= Theme::iListSecondaryWidth + Theme::iListItemIndent;

  switch(odi)
  {
  case odiBackground:
    OptDrawBackground(hParent, hdc, rItem);
    break;
  case odiGroupHeader:
    OptDrawGroup(hParent, hdc, rItem);
    break;
  case odiPrimaryText:
    OptDrawPrimary(hParent, hdc, rc);
    break;
  case odiDialingNo:
    OptDrawDialingNo(hParent, hdc, rc);
    break;
  case odiPicture:
    OptDrawPicture(hParent, hdc, rc);
    break;
  case odiSecondaryText:
    OptDrawSecondary(hParent, hdc, rItem);
    break;
  }
}

void CListItemCallLog::OptDrawSecondary(HWND hParent, HDC hdc, const RECT& rItem) const
{
  RECT rect = rItem;

  // Call Log picture
  if (cEntry && Info_GetCallType(hParent) == ctAll)
  {
    ESpecialIcons icon;
    switch(cEntry->callType)
    {
    case ctMissed:   icon = siCallLogMissed;   break;
    case ctIncoming: icon = siCallLogIncoming; break;
    case ctOutgoing: icon = siCallLogOutgoing; break;
    default:         icon = siNone;            break;
    }
    if (icon != siNone)
    {
      rect.left = rItem.right - Theme::iListItemIndent - (Theme::iListSecondaryWidth + Theme::SpecialIconsWidth) / 2;
      rect.top = rItem.bottom - (Theme::iListItemSize + Theme::SpecialIconsHeight) / 2;
      Theme::DrawSpecialIcon(hdc, rect.left, rect.top, icon);
    }
  }

  // Item Secondary Text
  if (nSecondaryTextLength > 0)
  {
    // secondary 1
    SetTextColor(hdc, GetSecondaryColor(hParent));
    if (nSecondaryText2Length > 0)
      rect.top = rItem.bottom - 2 - ((Theme::iListItemSize + Theme::SecondaryListFontSize * 2 + 3) / 2);
    else if (Settings::showDialingNumber && nDialingNumberLength > 0)
      rect.top = rItem.bottom - 2 - ((Theme::iListItemSize + Theme::DialingNoListFontSize + Theme::SecondaryListFontSize) / 2);
    else
      rect.top = rItem.bottom - 2 - ((Theme::iListItemSize + Theme::SecondaryListFontSize) / 2);
    rect.bottom = rect.top + Theme::SecondaryListFontSize;
    rect.right = rItem.right - Theme::iListItemIndent;
    rect.left = rect.right - Theme::iListSecondaryWidth;
    if (Settings::doFastGraphics)
      ExtTextOut(hdc, rect.left + Theme::iListSecondaryWidth / 2, rect.top,
        0, NULL, szSecondaryText, nSecondaryTextLength, 0);
    else
      ExtTextOut(hdc, rect.left + Theme::iListSecondaryWidth / 2, rect.top,
        ETO_CLIPPED, &rect, szSecondaryText, nSecondaryTextLength, 0);
    // secondary 2
    if (nSecondaryText2Length > 0)
    {
      SetTextColor(hdc, GetSecondary2Color(hParent));
      rect.top = rect.bottom + 3;
      rect.bottom = rect.top + Theme::SecondaryListFontSize;
      if (Settings::doFastGraphics)
        ExtTextOut(hdc, rect.left + Theme::iListSecondaryWidth / 2, rect.top,
          0, NULL, szSecondaryText2, nSecondaryText2Length, 0);
      else
        ExtTextOut(hdc, rect.left + Theme::iListSecondaryWidth / 2, rect.top,
          ETO_CLIPPED, &rect, szSecondaryText2, nSecondaryText2Length, 0);
    }
  }
}

bool CListItemCallLog::GetDetails(HWND hParent, CListDetails& details)
{
  if (cEntry == NULL)
    return false;

  // try to get oId
  if (oId == 0)
    oId = FindOid(hParent, cEntry->szName, cEntry->szNumber, true);

  details.Create(oId, siContactNoPicture, szPrimaryText);

  // Update item picture if it has been loaded from cache
  if (oId != 0 && picture && picture->isLoadedFromCache)
    PoomPictures::Add(oId, Theme::ListPictureWidth, Theme::ListPictureHeight, true);

  //
  // HEADER (company, job title, birthday)
  //
  TCHAR name[PRIMARY_TEXT_LENGTH] = {0};
  if (GetNameFromNumber(hParent, name, cEntry->szNumber, true))
  {
    // Use POOM data
    details.AddHeader(name);
  }
  else if (cEntry->hasName() && cEntry->hasNumber() && !_tcsicmp(cEntry->szName,cEntry->szNumber))
  {
    // Duplicate name/number should show UNKNOWN as the details title
    details.AddHeader(Language::unknown);
  }
  else 
  {
    // use default data
    details.AddHeader(cEntry->hasName() ? cEntry->szName : Language::unknown);
  }

  //
  // DETAILS
  //
  const TCHAR* callTypeStr = TEXT("");
  switch(cEntry->callType)
  {
  case ctMissed:
    callTypeStr = Language::missed;
    break;
  case ctIncoming:
    callTypeStr = Language::incoming;
    break;
  case ctOutgoing:
    callTypeStr = Language::outgoing;
    break;
  }
  details.AddString(iptCallType, callTypeStr);

  if (cEntry->hasNumber())
  {
    details.AddAction(cEntry->numberType, cEntry->szNumber, liaCall, liaSms);
  }

  TCHAR tempStr[PRIMARY_TEXT_LENGTH] = {0};
  printDate(tempStr, PRIMARY_TEXT_LENGTH, cEntry->ftStart);
  details.AddAction(iptDate, tempStr, liaText);

  if (cEntry->callType != ctMissed)
  {
    printDuration(tempStr, PRIMARY_TEXT_LENGTH, cEntry->ftStart, cEntry->ftEnd);
    details.AddString(iptDuration, tempStr);
  }

  if (cEntry->szRepeatingInfo[0] != 0)
  {
    details.AddText(iptRepeated, cEntry->szRepeatingInfo);
  }

  return true;
}

void CListItemCallLog::printDate(TCHAR * str, int strLength, FILETIME ft)
{
  FILETIME ftLocal;
  SYSTEMTIME st;
  TCHAR tmp[SECONDARY_TEXT_LENGTH];
  FileTimeToLocalFileTime(&ft, &ftLocal);
  FileTimeToSystemTime(&ftLocal, &st);
  GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, tmp, SECONDARY_TEXT_LENGTH);
  str[0] = 0;
  StringCchCat(str, strLength, tmp);
  StringCchCat(str, strLength, TEXT(" "));
  GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, tmp, SECONDARY_TEXT_LENGTH);
  StringCchCat(str, strLength, tmp);
}

void CListItemCallLog::printDuration(TCHAR * str, int strLength, FILETIME ftStart, FILETIME ftEnd)
{
  LARGE_INTEGER liStart;
  LARGE_INTEGER liEnd;
  LONGLONG duration;
  int hours;
  int minutes;

  liStart.HighPart = ftStart.dwHighDateTime;
  liStart.LowPart = ftStart.dwLowDateTime;
  liEnd.HighPart = ftEnd.dwHighDateTime;
  liEnd.LowPart = ftEnd.dwLowDateTime;
  duration = liEnd.QuadPart - liStart.QuadPart;
  duration /= 10000000; //100 nanosecond intervals -> seconds

  if (duration > 3600) {
      hours = (int)duration / 3600;
      duration -= hours * 3600;
      minutes = (int)duration / 60;
      duration -= minutes * 60;
      StringCchPrintf(str, strLength, TEXT("%d:%02d:%02d"), 
          hours, minutes, duration);
  }
  else if (duration > 60) {
      minutes = (int)duration / 60;
      duration -= minutes * 60;
      StringCchPrintf(str, strLength, TEXT("%d:%02d"), minutes, duration);
  }
  else {
      StringCchPrintf(str, strLength, TEXT("%d "), duration);
      StringCchCat(str, strLength, Language::seconds);
  }
}

CListItemPoom::CListItemPoom()
  : isFavorite(false), dialingNumberProp(0)
{
}

void CListItemPoom::Clear()
{
  isFavorite = false; 
  dialingNumberProp = 0;

  CListItemBase::Clear();
}

bool CListItemPoom::IsCommandValid(MenuCommands eCmd)
{
  switch(eCmd)
  {
  case CMD_DETAILS_BACK:
    return true;
  // Poom Details
  case CMD_DETAILS_VIEW:
  case CMD_DETAILS_EDIT:
  case CMD_DETAILS_DELETE:
  case CMD_DETAILS_FAVORITE:
  case CMD_DETAILS_SET_PICTURE:
  case CMD_DETAILS_SET_RINGTONE:
    return true;
  }

  return false;
}

bool CListItemPoom::OnCommand(HWND hParent, MenuCommands eCmd)
{
  switch(eCmd)
  {
  case CMD_DETAILS_VIEW:
    return Display(hParent, false);

  case CMD_DETAILS_EDIT:
    return Display(hParent, true);

  case CMD_DETAILS_DELETE:
    return PoomData::DeleteContact(oId);

  case CMD_DETAILS_FAVORITE:
    return ToggleFavorite(hParent);

  case CMD_DETAILS_SET_PICTURE:
    return SetPicture(hParent);

  }

  return false;
}

COLORREF CListItemPoom::GetPrimaryColor(HWND hParent) const
{
  COLORREF primClr;

  if (this->GetHovered())
    primClr = Theme::rgbListItemSelectedText;
  else if (Info_GetCurrentTab(hParent) == tabContacts && Settings::highlightFavsInList && isFavorite)
    primClr = Theme::rgbListItemFavoriteText;
  else
    primClr = Theme::rgbListItemText;

  return primClr;
}

bool CListItemPoom::Display(HWND hWnd, bool edit)
{
  IDispatch* pDisp = NULL;
  IItem* pItem = NULL;
  HRESULT hr;

  if (FAILED(PoomData::GetApp()->GetItemFromOid(oId, &pDisp)))
    return false;
  if (FAILED(pDisp->QueryInterface(IID_IItem, (LPVOID*)&pItem)))
  {
    pDisp->Release();
    return false;
  }
  if (edit)
    hr = pItem->Edit(hWnd);
  else
    hr = pItem->Display(hWnd);

  pItem->Release();
  pDisp->Release();

  return SUCCEEDED(hr);
}

bool CListItemPoom::ToggleFavorite(HWND hWnd)
{
  IDispatch* pDisp = NULL;
  IItem* pItem = NULL;
  HRESULT hr;

  LPCWSTR lpCategory = Settings::favorite_category;

  if (FAILED(PoomData::GetApp()->GetItemFromOid(oId, &pDisp)))
    return false;
  if (FAILED(pDisp->QueryInterface(IID_IItem, (LPVOID*)&pItem)))
  {
    pDisp->Release();
    return false;
  }

  // update the categories in the POOM
  if (isFavorite)
    hr = pItem->RemoveCategory(lpCategory);
  else
    hr = pItem->AddCategory(lpCategory);

  if (SUCCEEDED(hr))
  {
    isFavorite = !isFavorite;
    hr = pItem->Save();
  }

  // save and clean up
  pItem->Release();
  pDisp->Release();

  return SUCCEEDED(hr);
}

bool CListItemPoom::SetPicture(HWND hWnd)
{
  TCHAR    szFile[MAX_PATH];
  OPENFILENAMEEX  ofnex = {0};

  ofnex.lStructSize     = sizeof(ofnex);
  ofnex.hwndOwner       = hWnd;
  ofnex.lpstrFile       = szFile;
  ofnex.nMaxFile        = sizeof(szFile)/sizeof(szFile[0]);
  ofnex.lpstrFilter     = TEXT("JPEG (*.jpg; *.jpeg))\0*.jpg;*.jpeg\0PNG (*.png)\0*.png\0GIF (*.gif)\0*.gif\0");
  ofnex.ExFlags         = OFN_EXFLAG_THUMBNAILVIEW;

  if (!GetOpenFileNameEx(&ofnex))
    return false;

  if (PoomPictures::AssignPicture(oId, szFile))
    return true;

  ::MessageBox(hWnd, Language::picsavefailed, Language::error, MB_OK | MB_ICONEXCLAMATION);
  return false;
}

bool CListItemPoom::GetDetails(HWND hParent, CListDetails& details)
{
  struct PoomDetailItem
  {
    ItemPropertyType type;
    DetailItemType   detailType;
    ListItemAction   action;
    ListItemAction   subAction;
  };

  details.Create(oId, siContactNoPicture, szPrimaryText);

  // GET CURRENT CONTACT
  IDispatch* pDisp = NULL;
  IItem* pItem = NULL;
  if (FAILED(PoomData::GetApp()->GetItemFromOid(oId, &pDisp)))
    return false;

  if (FAILED(pDisp->QueryInterface(IID_IItem, (LPVOID*)&pItem)))
  {
    pDisp->Release();
    return false;
  }

  // Update item picture if it has been loaded from cache
  if (picture && picture->isLoadedFromCache)
    PoomPictures::Add(oId, Theme::ListPictureWidth, Theme::ListPictureHeight, true);

  // READ ALL PROPERTIES IN A ROW
  static CEPROPID arrProps[] = {
      // header
      PIMPR_COMPANY_NAME,
      PIMPR_DEPARTMENT,
      PIMPR_JOB_TITLE,
      PIMPR_BIRTHDAY,
      PIMPR_ANNIVERSARY,
      // numbers
      PIMPR_MOBILE_TELEPHONE_NUMBER,
      PIMPR_HOME_TELEPHONE_NUMBER,
      PIMPR_HOME2_TELEPHONE_NUMBER,
      PIMPR_BUSINESS_TELEPHONE_NUMBER,
      PIMPR_BUSINESS2_TELEPHONE_NUMBER,
      PIMPR_COMPANY_TELEPHONE_NUMBER,
      PIMPR_CAR_TELEPHONE_NUMBER,
      PIMPR_RADIO_TELEPHONE_NUMBER,
      PIMPR_HOME_FAX_NUMBER,
      PIMPR_BUSINESS_FAX_NUMBER,
      PIMPR_PAGER_NUMBER,
      // web
      PIMPR_EMAIL1_ADDRESS,
      PIMPR_EMAIL2_ADDRESS,
      PIMPR_EMAIL3_ADDRESS,
      PIMPR_WEB_PAGE,
      PIMPR_IM1_ADDRESS,
      PIMPR_IM2_ADDRESS,
      PIMPR_IM3_ADDRESS,
      // footer
      PIMPR_NICKNAME,
      PIMPR_HOME_ADDRESS,
      PIMPR_BUSINESS_ADDRESS,
      PIMPR_OTHER_ADDRESS,
      PIMPR_ANNIVERSARY,
      PIMPR_CATEGORIES
  };
  static int numProps = sizeof(arrProps) / sizeof(CEPROPID);

  // item type, detail type, action, sub action
  static PoomDetailItem arrPropData[] = {
      { iptMobileNumber,     ditAction, liaCall, liaSms },   // numbers
      { iptHomeNumber,       ditAction, liaCall, liaSms },
      { iptHomeNumber2,      ditAction, liaCall, liaSms },
      { iptBusinessNumber,   ditAction, liaCall, liaSms },
      { iptBusinessNumber2,  ditAction, liaCall, liaSms },
      { iptCompanyNumber,    ditAction, liaCall, liaSms },
      { iptCarNumber,        ditAction, liaCall, liaSms },
      { iptRadioNumber,      ditAction, liaCall, liaSms },
      { iptHomeFax,          ditAction, liaCall, liaNone },
      { iptBusinessFax,      ditAction, liaCall, liaNone },
      { iptPager,            ditAction, liaCall, liaNone },
      { iptEmail,            ditAction, liaEmail,liaNone },        // web
      { iptEmail2,           ditAction, liaEmail,liaNone },
      { iptEmail3,           ditAction, liaEmail,liaNone },
      { iptWebPage,          ditAction, liaUrl,  liaNone },
      { iptInstantMessenger, ditString, liaNone, liaNone },
      { iptInstantMessenger, ditString, liaNone, liaNone },
      { iptInstantMessenger, ditString, liaNone, liaNone },
      { iptNickname,         ditString, liaNone, liaNone },
      { iptHomeAddress,      ditText,   liaNone, liaNone },
      { iptBusinessAddress,  ditText,   liaNone, liaNone },
      { iptOtherAddress,     ditText,   liaNone, liaNone },
      { iptAnniversary,      ditText,   liaNone, liaNone },
      { iptCategories,       ditText,   liaNone, liaNone }
    };
  static int numPropData = sizeof(arrPropData) / sizeof(PoomDetailItem);

  PropertyValues vals;
  bool res = PoomData::GetItemProperties(pItem, arrProps, numProps, vals);
  if (!res)
  {
    pItem->Release();
    pDisp->Release();
    return false;
  }

  int nProp = 0;

  //
  // ADD HEADER (company, job title, birthday)
  //
  TCHAR* company = NULL;
  TCHAR* department = NULL;
  TCHAR* position = NULL;
  TCHAR birthday[PRIMARY_TEXT_LENGTH] = {0};
  TCHAR anniversary[PRIMARY_TEXT_LENGTH] = {0};
  if (EdbData::IsValidString(vals[nProp]))
    company = vals[nProp].val.lpwstr;
  nProp++;
  if (EdbData::IsValidString(vals[nProp]))
    department = vals[nProp].val.lpwstr;
  nProp++;
  if (EdbData::IsValidString(vals[nProp]))
    position = vals[nProp].val.lpwstr;
  nProp++;
  EdbData::GetPropertyDateValue(vals[nProp], birthday, PRIMARY_TEXT_LENGTH, DATE_LONGDATE);
  nProp++;
  
  //lcm new layout
  if (Theme::HasSmallIcons)
  {
    EdbData::GetPropertyDateValue(vals[nProp], anniversary, PRIMARY_TEXT_LENGTH, DATE_LONGDATE);
    nProp++;
  }
  details.AddHeader(szPrimaryText, company, department, position, birthday, anniversary);

  // ADD RINGTONE
  if (Settings::showRingtonesInDetails) 
  {
    TCHAR szRingtone[MAX_PATH] = {0};
    if (GetContactRingtone(pItem, szRingtone, sizeof(szRingtone)))
      details.AddRingtone(szRingtone);
  }

  //
  // ADD CONTACT DETAILS
  //
  int numCalls = 0;
  for (int i = 0; i < numPropData; i++)
  {
    if (arrPropData[i].detailType == ditAction &&
        arrPropData[i].action == liaCall &&
        EdbData::IsValidString(vals[nProp + i]))
      numCalls++;
  }
  TCHAR  szText[PRIMARY_TEXT_LENGTH];
  TCHAR* szData = NULL;
  for (int i = 0; i < numPropData; i++, nProp++)
  {
    // get data
    szData = NULL;
    if (LOWORD(vals[nProp].propid) == CEVT_LPWSTR)
    {
      if (EdbData::IsValidString(vals[nProp]))
        szData = vals[nProp].val.lpwstr;
    }
    else if (EdbData::GetPropertyValue(vals[nProp], szText, PRIMARY_TEXT_LENGTH))
    {
      szData = szText;
    }
    if (szData == NULL)
      continue;

    //lcm new layout
    if ((arrPropData[i].type == iptAnniversary) && Theme::HasSmallIcons)
      continue;

    // add
    switch(arrPropData[i].detailType)
    {
    case ditAction:
      details.AddAction(arrPropData[i].type, szData, arrPropData[i].action, arrPropData[i].subAction,
                        numCalls > 1 && (vals[nProp].propid == dialingNumberProp),
                        vals[nProp].propid);
      break;
    case ditString:
      details.AddString(arrPropData[i].type, szData);
      break;
    case ditText:
      details.AddText(arrPropData[i].type, szData);
      break;
    }
  }

  // Set favorite flag
  if (EdbData::IsValidString(vals[numProps - 1]))
    isFavorite = (NULL != _tcsstr(vals[numProps - 1].val.lpwstr, Settings::favorite_category));
  else
    isFavorite = false;

  // Read Notes
  if (Settings::showNotesInDetails)
  {
    PropertyValues notes;
    if (PoomData::GetItemProperty(pItem, PIMPR_BODY_TEXT, notes))
    {
      if (EdbData::IsValidString(notes[0]))
      {
        details.AddText(iptNotes, notes[0].val.lpwstr);
        res = true;
      }
      notes.Free();
    }
  }

  // Cleanup
  vals.Free();
  pItem->Release();
  pDisp->Release();

  return true;
}

bool CListItemSim::IsCommandValid(MenuCommands eCmd)
{
  switch(eCmd)
  {
  case CMD_DETAILS_BACK:
    return true;
  // Sim Contact Details
  case CMD_DETAILS_SIM_NEWCONTACT:
  case CMD_DETAILS_SIM_GOTOOUTLOOK:
    return true;
  // Call Log Details
  case CMD_DETAILS_CL_NEWCONTACT:
  case CMD_DETAILS_CL_GOTOCONTACT:
  case CMD_DETAILS_CL_DELETECALL:
  // Poom Details
  case CMD_DETAILS_VIEW:
  case CMD_DETAILS_EDIT:
  case CMD_DETAILS_DELETE:
  case CMD_DETAILS_FAVORITE:
  // Contact Picture
  case CMD_DETAILS_SET_PICTURE:
  // Contact Ringtone
  case CMD_DETAILS_SET_RINGTONE:
  default:
    return false;
  }
}

bool CListItemSim::OnCommand(HWND hParent, MenuCommands eCmd)
{
  switch(eCmd)
  {
  case CMD_DETAILS_SIM_NEWCONTACT:
    return PoomData::CreateContact(szPrimaryText, GetDialingNumber());

  case CMD_DETAILS_SIM_GOTOOUTLOOK:
    return PhoneUtils::RunOutlook();
  }

  return false;
}

bool CListItemSim::GetDetails(HWND hParent, CListDetails& details)
{
  details.Create(0, siSimContact, szPrimaryText);
  // HEADER (company, job title, birthday)
  details.AddHeader(szPrimaryText);
  // PHONE NUMBERS
  details.AddAction(iptMobileNumber, GetDialingNumber(), liaCall, liaSms);

  return true;
}

bool CListItemNewContact::OnMouseClick(HWND hParent, int x, int y)
{
  return OnDefaultAction(hParent);
}

bool CListItemNewContact::OnKeyUp(HWND hParent, int vKey)
{
  switch(vKey)
  {
  case VK_TACTION:
    return OnDefaultAction(hParent);
  case VK_TTALK:
    if (Settings::enablePhoneKeyHook)
      return OnDefaultAction(hParent);
  }
  return false;
}

bool CListItemNewContact::OnLongTap(HWND hParent)
{
  return OnDefaultAction(hParent);
}

bool CListItemNewContact::OnDefaultAction(HWND hParent)
{
  return PoomData::CreateContact();
}
